package me.zhengjie.recitation.util;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Objects;

/**
 * SFTP 文件更新工具类，提供安全可靠的文件更新功能
 *
 * <p>特性包含：
 * <ul>
 *   <li>重试机制（网络波动自动恢复）</li>
 *   <li>原子文件替换（避免更新过程中出现文件损坏）</li>
 *   <li>临时文件清理（失败时自动清理残留）</li>
 *   <li>文件格式校验（MP3/WAV 格式限制）</li>
 * </ul>
 */
@Component
public class FileUpdateUtils {

    private static final int MAX_RETRY = 3;
    private static final int RETRY_DELAY_MS = 5000;

    @Autowired
    private GenericObjectPool<ChannelSftp> sftpPool; // SFTP 连接池

    /**
     * 更新远程服务器上的文件（原子化操作）
     *
     * @param remotePath 远程文件完整路径（如 /data/files/audio.mp3）
     * @param file       新文件内容（必须为 MP3/WAV 格式）
     * @throws IllegalArgumentException 当 remotePath 为空或目标文件不存在时抛出
     * @throws RuntimeException         当文件格式非法/SFTP操作失败时抛出
     */
    public void update(String remotePath, MultipartFile file) {
        Objects.requireNonNull(remotePath, "文件路径不能为空");
        validateFile(file);

        ChannelSftp channel = null;
        String tempPath = null;
        try {
            // 从池中获取 SFTP 连接
            channel = sftpPool.borrowObject();

            // 验证原文件存在
            verifyFileExists(channel, remotePath);

            // 生成唯一临时文件名
            tempPath = generateTempPath(remotePath);

            // 带重试机制的上传流程
            uploadWithRetry(channel, file, remotePath, tempPath);

        } catch (JSchException | SftpException | IOException e) {
            handleFinalFailure(channel, tempPath, remotePath, e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 归还连接
            if (channel != null) {
                try { sftpPool.returnObject(channel); } catch (Exception ignored) {}
            }
        }
    }

    /**
     * 带重试机制的文件上传流程
     *
     * @param channel    SFTP 通道连接
     * @param file       待上传文件
     * @param remotePath 目标文件路径
     * @param tempPath   临时文件路径（用于原子替换）
     * @throws SftpException 当超过最大重试次数或遇到不可恢复错误时抛出
     * @throws IOException   当文件流操作失败时抛出
     */
    private void uploadWithRetry(ChannelSftp channel, MultipartFile file,
                                 String remotePath, String tempPath)
            throws SftpException, IOException {

        int retryCount = 0;
        while (retryCount < MAX_RETRY) {
            try (InputStream is = file.getInputStream()) {
                // 尝试上传到临时文件
                channel.put(is, tempPath, ChannelSftp.OVERWRITE);

                // 原子替换原文件
                channel.rename(tempPath, remotePath);
                System.out.println("文件更新成功：" + remotePath);
                return;
            } catch (SftpException e) {
                retryCount++;
                if (retryCount >= MAX_RETRY || !isRecoverableError(e)) {
                    throw e; // 最终异常由外层处理
                }

                System.out.printf("上传失败（第%d次重试），原因：%s%n",
                        retryCount, e.getMessage());
                try {
                    Thread.sleep(RETRY_DELAY_MS);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("上传操作被中断", ie);
                }
            }
            // 重置输入流（重要！）
            file.getInputStream().close();
        }
    }

    /**
     * 生成临时文件路径（确保多线程并发安全）
     *
     * @param remotePath 原文件路径
     * @return 临时文件路径（格式：原路径_线程ID_时间戳.tmp）
     */
    private String generateTempPath(String remotePath) {
        return remotePath + "_" + Thread.currentThread().getId()
                + "_" + System.currentTimeMillis() + ".tmp";
    }

    /**
     * 验证远程文件存在性
     *
     * @param channel    SFTP 通道连接
     * @param remotePath 目标文件路径
     * @throws SftpException            当 SFTP 操作失败时抛出
     * @throws IllegalArgumentException 当文件不存在时抛出
     */
    private void verifyFileExists(ChannelSftp channel, String remotePath)
            throws SftpException {
        try {
            channel.stat(remotePath);
        } catch (SftpException e) {
            if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                throw new IllegalArgumentException("目标文件不存在: " + remotePath);
            }
            throw e;
        }
    }

    /**
     * 判断是否可恢复的 SFTP 错误
     *
     * @param e SFTP 异常对象
     * @return true 表示可重试的错误（如连接中断），false 表示致命错误
     */
    private boolean isRecoverableError(SftpException e) {
        return e.id == ChannelSftp.SSH_FX_CONNECTION_LOST
                || e.id == ChannelSftp.SSH_FX_FAILURE;
    }

    /**
     * 处理最终失败场景（清理临时文件 + 生成友好错误）
     *
     * @param channel    SFTP 通道连接（可能为 null）
     * @param tempPath   临时文件路径（可能为 null）
     * @param remotePath 目标文件路径
     * @param e          触发失败的异常对象
     * @throws RuntimeException 始终抛出，包含清理后的错误信息
     */
    private void handleFinalFailure(ChannelSftp channel, String tempPath,
                                    String remotePath, Exception e) {
        // 清理临时文件
        if (tempPath != null) {
            try {
                channel.rm(tempPath);
            } catch (Exception ex) {
                System.err.println("临时文件清理失败：" + ex.getMessage());
            }
        }

        // 构造友好错误信息
        String errorMsg = "文件更新失败：" + remotePath;
        if (e instanceof SftpException) {
            errorMsg += " (SFTP错误码：" + ((SftpException)e).id + ")";
        }
        errorMsg += "，原因：" + e.getMessage();

        throw new RuntimeException(errorMsg, e);
    }

    /**
     * 清理 SFTP 连接资源
     *
     * @param channel SFTP 通道连接（可为 null）
     * @param session SSH 会话连接（可为 null）
     */
    private void cleanupResources(ChannelSftp channel, Session session) {
        try {
            if (channel != null) channel.disconnect();
        } catch (Exception e) {
            System.err.println("通道关闭异常：" + e.getMessage());
        }
        try {
            if (session != null) session.disconnect();
        } catch (Exception e) {
            System.err.println("会话关闭异常：" + e.getMessage());
        }
    }

    /**
     * 验证上传文件的合法性
     *
     * @param file 待验证的文件对象
     * @throws RuntimeException 当文件为空/扩展名非法/格式不支持时抛出
     */
    private void validateFile(MultipartFile file) {
        if (file.isEmpty()) throw new RuntimeException("文件不能为空");

        String filename = file.getOriginalFilename();
        if (filename == null || filename.lastIndexOf(".") == -1) {
            throw new RuntimeException("文件名不合法");
        }

        String ext = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
        if (!Arrays.asList("mp3", "wav").contains(ext)) {
            throw new RuntimeException("仅支持 MP3/WAV 格式");
        }
    }
}